import java.net.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;

public class TaskExecutionChatServer { 
  private ArrayList<ChatClientRecord> groupList;
  private static final int NTHREADS = 100;
  private static final ThreadPoolExecutor exec
    = (ThreadPoolExecutor)Executors.newFixedThreadPool(NTHREADS);

  private class RegFailureException extends Exception { }

  private class ClientWatchdog extends Thread {
    public void run() {
      System.out.println("Client watchdog started");
      // Cleanup old clients
      while (true) {
        synchronized (groupList) {
          int size = groupList.size();
          for (int i = 0; i < size; i++) {
            ChatClientRecord rec = groupList.get(i);
            long currentTime = System.currentTimeMillis();
            if (currentTime - rec.lastHeartbeat > 60*1000) {
              System.out.println("Removing idle client " + rec.name);
              groupList.remove(i); 
              break;
            }
          }
        }
      }
    }
  }

  public TaskExecutionChatServer(int serverPort) {
    // Keep a record of available clients
    groupList = new ArrayList<ChatClientRecord>();

    // Start up server
    ServerSocket serverSocket = null;
    try {
      serverSocket = new ServerSocket(serverPort);
      System.out.println("TaskExecutionChatServer started on port " + serverPort);
    } catch (IOException e) {
      System.err.println("Could not listen on port: " + serverPort);
      System.exit(1);
    }

    // Start idle client cleanup
    ClientWatchdog watch = new ClientWatchdog();
    watch.start();

    // Accept chat client connections
    while (true) {
      // Accept a new connection
      try {
        System.out.println("There are " + exec.getPoolSize() + " threads running");
        final Socket clientSocket = serverSocket.accept();
        Runnable task = new Runnable() {
          public void run() {
            handleRequest(clientSocket);
          }
        };
        exec.execute(task);
      } catch (IOException e) {
        System.err.println("Accept failed (IOException)");
      }
    }
  }

  private void handleRequest(Socket clientSocket) {
    try {
      System.out.println("Accepted connection from " 
          + clientSocket.getInetAddress().getHostName() 
          + ":" + clientSocket.getPort());
      ObjectInputStream in = new ObjectInputStream(clientSocket.getInputStream());
      ObjectOutputStream out = new ObjectOutputStream(clientSocket.getOutputStream());
      ChatMessage request = (ChatMessage)in.readObject();

      if (request.type == ChatMessage.REG_ATTEMPT) {
        // Attempt to register client
        ChatClientRecord rec = request.clientRec;
        String regName = rec.name;
        try {
          for (ChatClientRecord record : groupList)
            if (record.name.equals(regName))
              throw new RegFailureException();
          rec.lastHeartbeat = System.currentTimeMillis();
          synchronized (groupList) {
            groupList.add(rec);
          }
          ChatMessage msg = new ChatMessage(ChatMessage.REG_SUCCESS, null, null, null);
          out.writeObject(msg);
        } catch (RegFailureException e) {
          System.out.println("A client with name " + regName + " already exists.");
          ChatMessage msg = new ChatMessage(ChatMessage.REG_FAIL, null, null, null);
          out.writeObject(msg);
        }
      } else if (request.type == ChatMessage.HEARTBEAT) {
        // Update last heartbeat time to current time
        ChatClientRecord clientRec = request.clientRec;
        String name = clientRec.name;
        for (ChatClientRecord rec : groupList)
          if (rec.name.equals(name))
            rec.lastHeartbeat = System.currentTimeMillis();
      } else if (request.type == ChatMessage.GET_LIST) {
        // Send group list back to client
        ChatMessage msg = new ChatMessage(ChatMessage.GET_LIST, null, groupList, null);
        out.writeObject(msg);
        System.out.println("Sent list to client");
      }

      clientSocket.close();
    } catch (IOException e) {
      System.err.println("IOException occurred while handling client request");
    } catch (ClassNotFoundException e) {
      System.err.println("Could not find class ChatMessage");
      System.exit(1);
    }
  }

  public static void main(String[] args) {
    int serverPort;
    if (args.length == 1) {
      serverPort = Integer.parseInt(args[0]);
      new TaskExecutionChatServer(serverPort);
    }else{
      System.err.println("Usage: java TaskExecutionChatServer <serverPort>");
      System.exit(1);
    }
  }
}
